import os
import time 
import socket
import struct
import select

# 定义一个函数，用于连接到指定的地址，并发送ICMP数据包
def connect(addr, icmp_packet):
    # 实例化一个socket对象，ipv4，原始套接字，分配协议端口
    rawsocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
    # 发送数据到网络
    rawsocket.sendto(icmp_packet, (addr, 800))
    # 返回数据
    return rawsocket, addr

# 定义一个函数request，参数为seq
def request(seq):
    # 定义一个变量type，值为8
    type = 8
    # 定义一个变量code，值为0
    code = 0
    # 定义一个变量check_sum，值为0
    check_sum = 0
    # 定义一个变量ID，值为当前进程的进程号与0xffff取按位与的结果
    ID = os.getpid() & 0xffff
    data = b'abcdefghijklmnopqrstuvwabcdefghi'
    # 把字节打包成二进制数据，其中>（！）代表是是大端模式，B代表的是一个字节的无符号整数，H代表的是两个字节的无符号整数，d代表的是double
    icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
    # 获取校验和
    check_sum = checksum(icmp_packet)  # 获取校验和
    # 把校验和传入，再次打包
    icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
    # 返回打包后的数据
    return icmp_packet

def reply(rawsocket, ping_time, timeout=1):
    while True:
        # 开始时间
        started_time = time.time()
        # 实例化select对象，可读rawsocket，可写为空，可执行为空，超时时间
        recv = select.select([rawsocket], [], [], timeout)
        # 等待时间
        wait_for_time = (time.time() - started_time)
        # 如果没有返回可写内容判断为超时
        if recv[0] == []:
            return -1, -1
        # 记录接收时间
        received_time = time.time()
        # 设置接收的包的字节为1024字节
        received_packet, addr = rawsocket.recvfrom(1024)
        # 判断是否超时
        timeout = timeout - wait_for_time
        if timeout <= 0:
            return -1, -1, -1
        #获取ip报文的TTL
        ttl = received_packet[8]
        # 获取icmp报文的头部
        icmpHeader = received_packet[20:28]
        type, code, checksum, packet_id, sequence = struct.unpack(">BBHHH", icmpHeader)
        if type == 0:
            return received_time - ping_time, sequence, ttl

def checksum(data):
    n = len(data)
    m = n % 2
    sum = 0
    for i in range(0, n - m, 2):
        # 传入data以每两个字节（十六进制），第一字节在低位，第二个字节在高位
        sum += (data[i]) + ((data[i + 1]) << 8)
    if m:
        sum += (data[-1])
    # 将高于16位与低16位相加
    while sum >> 16:
        sum = (sum >> 16) + (sum & 0xffff)
    # 如果还有高于16位，将继续与低16位相加
    answer = ~sum & 0xffff
    #  主机字节序转网络字节序列
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer


def ping(host, count = 4, timeout = 2):
    addr = socket.gethostbyname(host)  # 获取ip地址
    print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host, addr))
    lost = 0
    accept = 0
    sumtime = 0.0
    times = []  # 统计所有包的往返时间长度
    for i in range(count):
        i += 1
        icmp_packet = request(i)
        try:
            rawsocket, dst_addr = connect(addr, icmp_packet)
            time0, sequence, ttl = reply(rawsocket, time.time(), timeout)
        except Exception as e:
            time0=-1
        if time0 < 0:
            print("请求超时。")
            lost += 1
            times.append(timeout * 1000)
        else:
            time0 = time0 * 1000
            # 延迟两秒
            time.sleep(2)
            if(time0>=1):
                print("来自 {0} 的回复: 字节=32 seq = {1} 时间={2:.2f}ms TTL={3}".format(dst_addr, sequence, time0, ttl))
            else:
                print("来自 {0} 的回复: 字节=32 seq = {1} 时间<1ms TTL={2}".format(dst_addr,  sequence, ttl))
            accept += 1
            sumtime += time0
            times.append(time0)
    # 统计
    # 打印出地址addr的Ping统计信息
    print('{0} 的 Ping 统计信息:'.format(addr))
    # 打印出已发送的数据包数量、已接收的数据包数量、丢失的数据包数量，以及丢失的数据包所占比例
    print('\t数据包: 已发送 = {0}，已接收 = {1}，丢失 = {2} ({3}% 丢失)，'
          .format(count, accept, lost, lost // (lost + accept) * 100,))
    if accept > 0:
        # 打印出往返行程的估计时间（以毫秒为单位）
        print('往返行程的估计时间(以毫秒为单位):')
        # 打印出最短、最长、平均的时间
        print('\t最短 = {0:.2f}ms，最长 = {1:.2f}ms，平均 = {2:.2f}ms'
            .format(min(times),max(times), sum(times) // (lost + accept)))

if(__name__ == "__main__"):
     address = input("ping ")
     ping(address)

